//this file define model observer pattern
//andycai.sc@gmail.com 2011-08-31

#ifndef GMODELOBSERVER_H
#define GMODELOBSERVER_H

#include "GTpl/GMap.h"
#include "GParallel/GLocker.h"
#include "GDomainObject.h"

#include <memory>
#include <set>
using std::set;

class GAbsObserver;

//define model class
class GAbsModel
{
public:
	GAbsModel(GLocker* locker = 0)
	{
        m_observers = new GHashMultiMap<long, GAbsObserver*>();

        if (0 == locker)
        {
            m_obsLocker.reset(new GLocker());
        }
        else
        {
            m_obsLocker.reset(locker);
        }
	}

	virtual ~GAbsModel()
	{
	    delete m_observers;
	}

	virtual void attach(GAbsObserver* obs, const long& event);
	virtual void detach(GAbsObserver* obs);

    virtual void notify(long event, void* data);
	virtual void notify(GDomainEvent* evenObj);

protected:
	GMap<long, GAbsObserver*>* m_observers;
    std::auto_ptr<GLocker> m_obsLocker;
};

class GAbsObserver
{
public:
	virtual ~GAbsObserver() { }

	void addModel(GAbsModel* model);
	void removeModel(GAbsModel* model);

	virtual void update(GDomainEvent* eventObj, GAbsModel* model) = 0;

protected:
	GAbsObserver(GLocker* locker = 0)
	{
        if (0 == locker)
        {
            m_modsLocker.reset(new GLocker());
        }
        else
        {
            m_modsLocker.reset(locker);
        }
	}

	virtual void refresh(GAbsModel* model) {}
	virtual void clean(GAbsModel* model) {}
	set<GAbsModel*> m_models;
	std::auto_ptr<GLocker> m_modsLocker;
};

inline
void GAbsModel::attach(GAbsObserver* obs, const long& event)
{
	m_obsLocker->lock();
	GMap<long, GAbsObserver*>::Range raIts;
	raIts = m_observers->equal_range(event);
	GMap<long, GAbsObserver*>::IterPtr it;
	for (it = raIts.first; it != raIts.last; ++it)
	{
		if (obs == it->getValue())
			break;
	}
	if (it == raIts.last)
	{
		m_observers->insert(event, obs);
		m_obsLocker->unlock();
		obs->addModel(this);
		return;
	}
	m_obsLocker->unlock();
}

inline
void GAbsModel::detach(GAbsObserver* obs)
{
    m_obsLocker->lock();
	GMap<long, GAbsObserver*>::IterPtr it = m_observers->begin();
	while (it != m_observers->end())
	{
		if (obs == it->getValue())
		{
		    GMap<long, GAbsObserver*>::IterPtr curIt = it++;
			m_observers->erase(curIt);
		}
		else
			++it;
	}
	m_obsLocker->unlock();
	obs->removeModel(this);
}

inline
void GAbsModel::notify(long event, void* data)
{
    GDomainEvent eventObj(event, data);
    notify(&eventObj);
}

inline
void GAbsModel::notify(GDomainEvent* evenObj)
{
    GScopedLocker scopeLocker(*m_obsLocker);
	GMap<long, GAbsObserver*>::Range raIts;
	raIts = m_observers->equal_range(evenObj->getEvent());
	GMap<long, GAbsObserver*>::IterPtr it;
	for (it = raIts.first; it != raIts.last; ++it)
	{
		GAbsObserver* obs = it->getValue();
		obs->update(evenObj, this);
	}
}

inline
void GAbsObserver::addModel(GAbsModel* model)
{
	m_modsLocker->lock();
	if (m_models.find(model) == m_models.end())
	{
		m_models.insert(model);
		m_modsLocker->unlock();
		refresh(model);
		return;
	}
	m_modsLocker->unlock();
}

inline
void GAbsObserver::removeModel(GAbsModel* model)
{
	m_modsLocker->lock();
	set<GAbsModel*>::iterator it;
	if ((it = m_models.find(model)) != m_models.end())
	{
		m_models.erase(it);
		m_modsLocker->unlock();
		clean(model);
		return;
	}
	m_modsLocker->unlock();
}

//you can use the two classes belows when the domain object changed have
//many observers, or you should use GDomainObject at first, or you know
//there must be multi-observers so you should use GAbsModel directly
template <typename T>
class GDomainModel : public GAbsModel, public GDataReceiver
{
public:
	GDomainModel(T* domainObj = 0) : m_domainObj(domainObj)
	{
	}
	virtual ~GDomainModel() {}

	T* operator->() const
	{
		return m_domainObj;
	}

	T* get() const
	{
		return m_domainObj;
	}

	void process(GDomainEvent* dEvent, GDomainObject* dObject)
	{
		notify(dEvent);
	}

protected:
	T* m_domainObj;
};

template <typename TDomain>
class GReceiverObserver : public GAbsObserver
{
public:
    GReceiverObserver(GDataReceiver* dataReceiver) : m_dataReceiver(dataReceiver)
    {
    }

    virtual ~GReceiverObserver() {}

    void refresh(GAbsModel* model)
    {
        GDomainModel<TDomain>* domainModel = dynamic_cast< GDomainModel<TDomain>* >(model);
        if (domainModel != 0)
        {
            m_dataReceiver->set(domainModel->get());
        }
    }

    void clean(GAbsModel* model)
    {
        GDomainModel<TDomain>* domainModel = dynamic_cast< GDomainModel<TDomain>* >(model);
        if (domainModel != 0)
        {
            m_dataReceiver->clear(domainModel->get());
        }
    }

    void update(GDomainEvent* eventObj, GAbsModel* model)
    {
        GDomainModel<TDomain>* domainModel = dynamic_cast< GDomainModel<TDomain>* >(model);
        GDomainObject* domainObj = domainModel != 0 ? domainModel->get() : 0;
        m_dataReceiver->process(eventObj, domainObj);
    }

protected:
    GDataReceiver* m_dataReceiver;
};


#endif
